#include <string.h>
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include "fd_list.h"
#include "bstrlib.h"


struct sfd_item{
    int n_fd; /*file descriptor*/
    int cmd; /*numero de comando*/
    int file_n; /*numero de archivo que estoy bajando*/
    int part_n; /*numero de parte*/
    int cant; /*cuantos pedazos me faltan de bajar*/
    int answ; /*codigo de respuesta*/
    bool f_recv; /*termine de recibir?*/
    bool f_attnd; /*termine de atenderlo?*/
    bool send; /*estoy enviando?*/
    bool index; /*es el tracker?*/
    bool node; /*es un peer?*/
    bstring recv_buf; /*buffer donde voy recibiendo datos*/
    char* ip_port; /*char con formato ip:port*/

    int offset;
    int amount;
    int transferred;
};

struct sfd_list{
    fd_item *item;
    int size;
};

/*
    Devuelve un item vacio
*/
fd_item
fd_item_create(void){
    struct sfd_item *item;
    item = calloc(1, sizeof(struct sfd_item));
    item->n_fd=-1;
    item->cmd=-1;
    item->file_n=-1;
    item->part_n=-1;
    item->cant=-1;
    item->answ=-2;
    item->f_recv=true;
    item->f_attnd=false;
    item->send=false;
    item->index=false;
    item->node=true;
    item->recv_buf = bfromcstr("");
    item->ip_port = calloc(1, sizeof(char));
    if (item->ip_port == NULL){
        perror("fd_item_create_ip_port_calloc");
        return NULL;
    }
    item->offset=-1;
    item->amount=-1;
    item->transferred=-1;
    return item;
}

/*
    Permite setear el numero de fd del item
*/
void
fd_item_set_fd(fd_item item, int fd){
    item->n_fd = fd;
}

/*
    Obtenemos el fd
*/
int
fd_item_get_fd(fd_item item){
    return item->n_fd;
}

/*
    Funcion para setear el valor del campo command:
    0:hello
    1:get_file_listing
    2:get_sources
    3:get_metadata
    4:new_fragment
    5:get_slice
    6:quit
*/
void
fd_item_set_cmd(fd_item item, int cmd){
    item->cmd = cmd;
}

/*
    Obtenemos el comando con el que esta seteado el fd
*/
int
fd_item_get_cmd(fd_item item){
    return item->cmd;
}

/*
    Funcion para setear el numero de archivo que estoy bajando
*/
void
fd_item_set_file_n(fd_item item, int file_n){
    item->file_n = file_n;
}

/*
    Funcion para obtener el numero de archivo que estoy bajando
*/
int
fd_item_get_file_n(fd_item item){
    return item->file_n;
}

/*
    Funcion para setear el numero de parte que estoy bajando
*/
void
fd_item_set_part_n(fd_item item, int part_n){
    item->part_n = part_n;
}

/*
    Funcion para obtener el numero de parte que estoy bajando
*/
int
fd_item_get_part_n(fd_item item){
    return item->part_n;
}

/*
    Funcion para setear cuantas partes me faltan
*/
void
fd_item_set_cant(fd_item item, int cant){
    item->cant = cant;
}

/*
    Funcion para obtener cuantas partes me faltan
*/
int
fd_item_get_cant(fd_item item){
    return item->cant;
}

/*
    Funcion para setear el codigo de respuesta
*/
void
fd_item_set_answ(fd_item item, int answ){
    item->answ = answ;
}

/*
    Funcion para obtener el codigo de respuesta
*/
int
fd_item_get_answ(fd_item item){
    return item->answ;
}

/*
    Funcion para anunciar que ya termine de recibir
*/
void
fd_item_finished_receiving(fd_item item){
    item->f_recv = true;
}

/*
    Funcion para anunciar que no terminé de recibir
*/
void
fd_item_not_finished_receiving(fd_item item){
    item->f_recv = false;
}

/*
    Funcion para preguntar si termine de recibir
*/
bool
fd_item_has_finished_receiving(fd_item item){
    return item->f_recv;
}

/*
    Funcion para anunciar que ya termine de atender el nodo
*/
void
fd_item_finished_attending(fd_item item){
    item->f_attnd = true;
}

/*
    Funcion para anunciar que no terminé de atender el nodo
*/
void
fd_item_not_finished_attending(fd_item item){
    item->f_attnd = false;
}

/*
    Funcion para preguntar si termine de atender el nodo
*/
bool
fd_item_has_finished_attending(fd_item item){
    return item->f_attnd;
}

/*
    Funcion para anunciar que estoy enviando
*/
void
fd_item_sending(fd_item item){
    item->send = true;
}

/*
    Funcion para anunciar que estoy recibiendo
*/
void
fd_item_receiving(fd_item item){
    item->send = false;
}

/*
    Funcion para preguntar si estoy enviando
*/
bool
fd_item_am_i_sending(fd_item item){
    return item->send;
}


/*
    Funcion para anunciar que es el tracker
*/
void
fd_item_index(fd_item item){
    item->index = true;
}

/*
    Funcion para anunciar que no es un tracker
*/
void
fd_item_not_index(fd_item item){
    item->index = false;
}

/*
    Funcion para preguntar si es un tracker
*/
bool
fd_item_is_tracker(fd_item item){
    return item->index;
}

/*
    Funcion para anunciar que es un peer
*/
void
fd_item_peer(fd_item item){
    item->node = true;
}

/*
    Funcion para anunciar que no es un peer
*/
void
fd_item_not_peer(fd_item item){
    item->node = false;
}

/*
    Funcion para preguntar si es un peer
*/
bool
fd_item_is_peer(fd_item item){
    return item->node;
}

/*
    Funcion para eliminar el contenido de
    recv_buf
*/
void
fd_item_clear_data(fd_item item){
    bdestroy (item->recv_buf);
    item->recv_buf = NULL;
    item->recv_buf = bfromcstr("");
}

/*
    Funcion para setear el campo recv_buf
*/
void
fd_item_set_data(fd_item item, bstring data){
    bdestroy (item->recv_buf);
    item->recv_buf = NULL;
    item->recv_buf = bstrcpy(data);
}

/*
    Funcion para obtener el campo recv_buf
*/
bstring
fd_item_get_data(fd_item item){
    bstring file_aux=NULL;
    file_aux = bstrcpy(item->recv_buf);
    return file_aux;
}

/*
    Funcion para setear el campo ip_port
    Devuelve 1 en caso de exito 0 en caso de error
*/
int
fd_item_set_ip_port(fd_item item, char* ip_port){
    free(item->ip_port);
    item->ip_port = calloc(strlen(ip_port), sizeof(char));
    if (item->ip_port == NULL){
        perror("fd_item_set_ip_port_calloc");
        return 0;
    }
    strncpy(item->ip_port, ip_port, strlen(ip_port));
    return 1;
}

/*
    Funcion para obtener el campo ip_port
*/
char*
fd_item_get_ip_port(fd_item item){
    char* ip_port;
    ip_port = calloc(strlen(item->ip_port), sizeof(char));
    if (ip_port == NULL){
        perror("fd_item_get_ip_port_calloc");
        return NULL;
    }
    strncpy(ip_port, item->ip_port, strlen(item->ip_port));
    return ip_port;
}

/*
    Seteamos el offset del archivo pedido
*/
void
fd_item_set_offset(fd_item item, int offset){
    assert(item != NULL);
    item->offset=offset;
}

/*
    Obtenemos el offset pedido
*/
int
fd_item_get_offset(fd_item item){
    assert(item != NULL);
    return item->offset;
}

/*
    Seteamos el amount pedido
*/
void
fd_item_set_amount(fd_item item, int amount){
    item->amount=amount;
}

/*
    Obetenemos el amount pedido
*/

int
fd_item_get_amount(fd_item item){
    assert(item != NULL);
    return item->amount;
}

/*
    Aca vamos cargando cuanto ya le hemos enviado al cliente
*/
void
fd_item_set_transferred(fd_item item, int transferred){ 
    assert(item != NULL);
    item->transferred=transferred;
}

/*
    Obtenemos informacion sobre cuanto hemos enviado al cliente
*/

int
fd_item_get_transferred(fd_item item){
    assert(item != NULL);
    return item->transferred;
}

/*
    Este es el destructor
*/
void
fd_item_destroy(fd_item item){
    assert(item != NULL);
    if(item->recv_buf != NULL){
        bdestroy (item->recv_buf);
        item->recv_buf = NULL;
    }
    if(item->ip_port != NULL){
        free(item->recv_buf);
        item->recv_buf = NULL;
    }
    free(item);
    item = NULL;
}

/*
    ACA EMPIEZAN LAS FUNCIONES DE LISTA
*/

/*
    Constructor de lista
*/
fd_list
fd_list_create(void){
    struct sfd_list *list;
    list = calloc(1,sizeof(struct sfd_list));
    if (list == NULL){
        perror("malloc");
        return NULL;
    }    
    list->item = calloc(1,sizeof(fd_item*));
    if (list->item == NULL){
        perror("malloc");
        return NULL;
    }
    list->size = 0;
    return list;
}

/*
    Aqui vamos agregando items a la lista de fd's
    le pasamos el fd, y el comando pedido, el fd es cargado
    en el ultimo lugar de la lista
*/
void
fd_list_add_fd(fd_list list, int fd){
    fd_item item;
    assert(list != NULL);
    assert(fd != 0);
    item = fd_item_create();
    fd_item_set_fd(item, fd);
    list->item = (fd_item*)realloc(list->item,\
                  sizeof(struct sfd_item)*((list->size)+1));
    list->item[list->size] = item;
    list->size = list->size + 1;
}

/*
    Obtenemos el fd
*/
int
fd_list_get_fd(fd_list list, int n){
    return fd_item_get_fd(fd_list_get_item(list, n));
}

/*
    Seteamos el comando para el fd
*/
void
fd_list_set_cmd(fd_list list, int n, int cmd){
    assert(list != NULL);
    fd_item_set_cmd(fd_list_get_item(list, n), cmd);
}

/*
    Obetenemos el comando con el que fue seteado el fd
*/
int
fd_list_get_cmd(fd_list list, int n){
    return fd_item_get_cmd(fd_list_get_item(list, n));
}

/*
    Seteo el numero de archivo que estoy bajando
*/
void
fd_list_set_file_n(fd_list list, int n, int i){
    fd_item_set_file_n(fd_list_get_item(list, n), i);
}

/*
    Pido el numero de archivo que estoy bajando
*/
int
fd_list_get_file_n(fd_list list, int n){
    return fd_item_get_file_n(fd_list_get_item(list, n));
}

/*
    Seteo el numero de parte que estoy bajando
*/
void
fd_list_set_part_n(fd_list list, int n, int i){
    fd_item_set_part_n(fd_list_get_item(list, n), i);
}

/*
    Pido el numero de parte que estoy bajando
*/
int
fd_list_get_part_n(fd_list list, int n){
    return fd_item_get_part_n(fd_list_get_item(list, n));
}

/*
    Seteo la cantidad de partes que me faltan de bajar de aca
*/
void
fd_list_set_cant(fd_list list, int n, int i){
    fd_item_set_cant(fd_list_get_item(list, n), i);
}

/*
    Pido la cantidad de partes que me faltan bajar de aca
*/
int
fd_list_get_cant(fd_list list, int n){
    return fd_item_get_cant(fd_list_get_item(list, n));
}

/*
    Funcion para setear el codigo de respuesta
*/
void
fd_list_set_answ(fd_list list, int n, int i){
    fd_item_set_answ(fd_list_get_item(list, n), i);
}

/*
    Pido el codigo de respuesta del item en n
*/
int
fd_list_get_answ(fd_list list, int n){
    return fd_item_get_answ(fd_list_get_item(list, n));
}

/*
 	Anunciamos que terminamos de recibir de aca
*/
void
fd_list_finished_receiving(fd_list list, int n){
	fd_item_finished_receiving(fd_list_get_item(list, n));
}

/*
 	Anunciamos qu eno terminamos de recibir todavia
*/
void
fd_list_not_finished_receiving(fd_list list, int n){
	fd_item_not_finished_receiving(fd_list_get_item(list, n));
}

/*
 	Preguntamos si terminamos de recibir
*/
bool
fd_list_has_finished_receiving(fd_list list, int n){
	return fd_item_has_finished_receiving(fd_list_get_item(list, n));
}

/*
    Anunciamos que terminamos de atender el item ubicado en n
*/
void
fd_list_finished_attending(fd_list list, int n){
    fd_item_finished_attending(fd_list_get_item(list, n));
}

/*
    Anunciamos que no terminamos de atender el item ubicado en n
*/
void
fd_list_not_finished_attending(fd_list list, int n){
    fd_item_not_finished_attending(fd_list_get_item(list, n));
}

/*
    Preguntamos si termine de atender el item ubicado en n
*/
bool
fd_list_has_finished_attending(fd_list list, int n){
    return fd_item_has_finished_attending(fd_list_get_item(list, n));
}

/*
    Anunciamos que estoy enviandole a este item
*/
void
fd_list_sending(fd_list list, int n){
    fd_item_sending(fd_list_get_item(list, n));
}

/*
    Anunciamos que estoy recibiendo desde este item
*/
void
fd_list_receiving(fd_list list, int n){
    fd_item_receiving(fd_list_get_item(list, n));
}

/*
    Pregunto si estoy enviando a este item
*/
bool
fd_list_am_i_sending(fd_list list, int n){
    return fd_item_am_i_sending(fd_list_get_item(list, n));
}

/*
 	Informo que este item es un tracker
*/
void
fd_list_index(fd_list list, int n){
	fd_item_index(fd_list_get_item(list, n));
}

/*
 	Informo que este item no es un tracker
*/
void
fd_list_not_index(fd_list list, int n){
	fd_item_not_index(fd_list_get_item(list, n));
}

/*
   Pregunto si este item es un tracker
*/
bool
fd_list_is_index(fd_list list, int n){
	return fd_item_is_tracker(fd_list_get_item(list, n));
}

/*
 	Informo que este item es un peer
*/
void
fd_list_peer(fd_list list, int n){
	fd_item_peer(fd_list_get_item(list, n));
}

/*
 	Informo que este item no es un peer
*/
void
fd_list_not_peer(fd_list list, int n){
	fd_item_not_peer(fd_list_get_item(list, n));
}

/*
 	Pregunto si este item es un peer
*/
bool
fd_list_is_peer(fd_list list, int n){
	return fd_item_is_peer(fd_list_get_item(list, n));
}

/*
    Funcion para eliminar el contenido del data del item n
*/
void
fd_list_clear_data(fd_list list, int n){
    fd_item_clear_data(fd_list_get_item(list, n));
}

/*
 	Seteo el campo recv_buf del item
*/
void
fd_list_set_data(fd_list list, int n, bstring data){
	fd_item_set_data(fd_list_get_item(list, n), data);
}

/*
    Funcion para obtener el campo recv_buf
*/
bstring
fd_list_get_data(fd_list list, int n){
	return fd_item_get_data(fd_list_get_item(list, n));
}

/*
    Funcion para setear el campo ip_port
    Devuelve 1 en caso de exito 0 en caso de error
*/
int
fd_list_set_ip_port(fd_list list, int n, char* ip_port){
	if(!fd_item_set_ip_port(fd_list_get_item(list, n), ip_port)){
		return 0;
	}
	else{
		return 1;
	}
}

/*
    Funcion para obtener el campo ip_port
*/
char*
fd_list_get_ip_port(fd_list list, int n){
	return fd_item_get_ip_port(fd_list_get_item(list, n));
}

/*
  Obtenemos el offset pedido en el fd ubicado en la posicion n de la lista
*/
int
fd_list_get_offset(fd_list list, int n){
    assert(list != NULL);
    return fd_item_get_offset(list->item[n]);
}

/*
  Seteamos el offset en el fd ubicado en la posicion n de la lista
*/
void
fd_list_set_offset(fd_list list, int n, int m){
    assert(list != NULL);
    fd_item_set_offset(list->item[n], m);
}

/*
  Obtenemos el amount del fd ubicado en la posicion n de la lista
*/
int
fd_list_get_amount(fd_list list, int n){
    assert(list != NULL);
    return fd_item_get_amount(list->item[n]);
}

/*
  Seteamos el amount en el fd ubicado en la posicion n de la lista
*/
void
fd_list_set_amount(fd_list list, int n, int m){
    assert(list != NULL);
    fd_item_set_amount(list->item[n], m);
}

/*
    Con esta funcion sacamos el elemento n de la lista
*/
void
fd_list_remove(fd_list list, int n){
    assert(list != NULL);
    fd_item_destroy(list->item[n]);
    while (n < (list->size)-1){
        list->item[n] = list->item[n+1];
        n++;
    }
    if (list->size > 0){
        list->item[list->size-1] = NULL;
        list->size = list->size - 1;
    }
}

/*
    Con esta funcion devolvemos la ubicacion en la lista
    de un fd
*/
int
fd_list_search(fd_list list, int fd){
    int i = 0;
    assert(list != NULL);
    assert(fd != 0);
    for (i=0;i<list->size;i++){
        if (fd==list->item[i]->n_fd){
            return i;
        }
    }
    return -1;
}

/*
    Obtenemos el item con indice n
*/
fd_item
fd_list_get_item(fd_list list, int n){
    assert(list != NULL);
    return list->item[n];
}

/*
  Obtenemos el numero de fd's en la lista (tamaño de la lista)
*/
int
fd_list_size(fd_list list){
    assert(list != NULL);
    return list->size;
}

/*
  Seteamos la cantidad transferida del archivo en el fd ubicado
  en la posicion n de la lista
*/
void
fd_list_set_transferred(fd_list list, int n, int transferred){
    assert(list != NULL);
    fd_item_set_transferred(list->item[n], transferred);
}

/*
  Obtenemos la cantidad transferida del archivo en el fd ubicado
  en la posicion n de la lista
*/
int
fd_list_get_transferred(fd_list list, int n){
    assert(list != NULL);
    return fd_item_get_transferred(list->item[n]);
}

/*
  Destructor del tipo fd_list
*/
void
fd_list_destroy(fd_list list){
    int i=0;
    for (i=0;i<list->size;i++){
        fd_list_remove(list, i);
    }
    free(list->item);
    list->item = NULL;
    free(list);
    list = NULL;
}
